#
# Copyright 2008-2010 Vadim Zhukov <persgray@gmail.com>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
#    list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY AGAVA COMPANY ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
# EVENT SHALL THE AGAVA COMPANY OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# The views and conclusions contained in the software and documentation are
# those of the authors and should not be interpreted as representing official
# policies, either expressed or implied, of the AGAVA Company.

package RB::Util;

use bytes;
use strict;

use Digest::MD5 qw/md5/;
use Exporter qw/import/;
use Math::BigInt;
use POSIX qw/mktime/;

our @EXPORT = qw/calcFileSizeMultiplier calcHumanSize
                 makeWhichFilter constructArchiveExt
                 chooseByHash wdayByName formatAnyDate daysInMonth/;

my @SizeMultipliers = (
    Math::BigInt->new(1),
    Math::BigInt->new(1024),
    Math::BigInt->new(1024*1024),
    Math::BigInt->new(1024*1024*1024),
    Math::BigInt->new(1024*1024*1024*1024),
    Math::BigInt->new(1024*1024*1024*1024*1024),
    Math::BigInt->new(1024*1024*1024*1024*1024*1024)
    );

my @BigSizeMultipliers = map { Math::BigInt->new($_) } @SizeMultipliers;
my @SizeSuffixies = (' ', 'K', 'M', 'G', 'T', 'P', 'E');
my %SuffixToMultiplier;

for (my $i = 0; $i < scalar(@SizeSuffixies); $i++) {
	$SuffixToMultiplier{$SizeSuffixies[$i]} = $SizeMultipliers[$i];
}

sub calcFileSizeMultiplier($) {
	my $suffix = uc(shift);
	$suffix = ' ' if $suffix eq 'B';
	return int($SuffixToMultiplier{$suffix}->bstr);
}

# Returns a decimal number with no more than 3 digits and one-letter size
# suffix. Single space is used as suffix if size is smaller than 10000 bytes.
sub calcHumanSize($) {
	my $size = shift;

	my $len = length $size;
	return "$size " if $len <= 3;    # Speed hack

	# Offset in @SizeMultipliers
	my $hindex = int($len / 3) - 1;
	$hindex++ if $len % 3 > 0;

	my $hsize = Math::BigInt->new($size);
	my $denom = Math::BigInt->new($SizeMultipliers[$hindex]);
	$hsize->bdiv($denom);
	return $hsize->bstr().$SizeSuffixies[$hindex];
}

# Creates filter based on user input
sub makeWhichFilter(@) {
	if (@_) {
		return join('|', @_);
	} else {
		return '.*';
	}
}

sub constructArchiveExt($$$) {
	my ($type, $format, $compressed) = (shift, shift, shift);
	my $ext;
	if ($type eq 'files') {
		$ext = '.'.$format;
	} elsif ($type eq 'mysql') {
		# Only "plain" format is supported
		$ext = '.sql';
	} elsif ($type eq 'postgres') {
		if ($format eq 'plain') {
			$ext = '.sql';
		} elsif ($format eq 'custom') {
			$ext = '.pg';
		} else {
			# "tar"
			$ext = '.'.$format;
		}
	} elsif ($type eq 'fs') {
		$ext = '.dump';
	}
	if ($compressed and $compressed !~ /^no(ne)?|false|off|disabled$/i) {
		$ext .= '.gz';
	}
	return $ext;
}

# Chooses an integer based on data provided. Data is hashed and hash represents
# the desired number. It's guaranteed that for same data and boundary result
# will be the same.
#
# Some ideas were taken from arc4random_uniform(3) in OpenBSD.
#
# Arguments:
#   $boundary - Specify upper boundary of possible numbers interval. Should not
#               be in range [0; 2^31) (it's NOT checked!).
#   $data     - Any data. The more entropy you give, the better result you have.
#
# Returns:
#   Integer number in [0; $boundary) interval.
sub chooseByHash ($$) {
	my ($boundary, $data) = @_;
	return 0 if $boundary < 2;
	
	my $len = length($boundary);
	my $min = 0x7FFFFFFF % $boundary;
	my $h;

	while (1) {
		$h = md5($data, $boundary);
		# 4 bytes is 32 bits
		$h = substr($h, $boundary % (length($h) - 4), 4);
		($h) = unpack('N', $h);
		last if $h > $min;
		$data .= ' ';
	}

	return $h % $boundary;
}

sub wdayByName ($) {
	my $name = lc $_[0];
	my $num = -1;
	   if ($name eq 'sun' or $name eq 'sunday')    { $num = 0; }
	elsif ($name eq 'mon' or $name eq 'monday')    { $num = 1; }
	elsif ($name eq 'tue' or $name eq 'tuesday')   { $num = 2; }
	elsif ($name eq 'wed' or $name eq 'wednesday') { $num = 3; }
	elsif ($name eq 'thu' or $name eq 'thursday')  { $num = 4; }
	elsif ($name eq 'fri' or $name eq 'friday')    { $num = 5; }
	elsif ($name eq 'sat' or $name eq 'saturday')  { $num = 6; }
	return $num;
}

sub formatAnyDate ($$$) {
	my ($year, $month, $mday) = @_;
	my $t = mktime(0, 0, 0, $mday, $month, $year-1900);
	return undef unless defined $t;
	my @ta = localtime $t;
        return sprintf("%0.4d%0.2d%0.2d", $ta[5]+1900, $ta[4]+1, $ta[3]);
}

sub daysInMonth ($$) {
	my ($year, $month) = @_;
	my $t = mktime(0, 0, 0, 0, $month+1, $year-1900);
	my @ta = localtime $t;
	if (wantarray) {
		return ($ta[5]-1900, $ta[4], $ta[3]);
	} else {
		return $ta[3];
	}
}

sub calcWeekday($$$) {
	my ($year, $month, $mday) = @_;
	my $t = mktime(0, 0, 0, $mday, $month, $year-1900);
	my @ta = localtime $t;
	return $ta[6];
}


1;
